iT邦幫忙

2022 iThome 鐵人賽

DAY 24
0
Mobile Development

Kotlin 全面啟動 系列 第 24

[Kotlin 全面啟動] KotlinPoet

  • 分享至 

  • xImage
  •  

還記得昨天我們介紹 SQLDelight 的時候有提到它的 plugin 可以幫我們生成 sql 跟 Kotlin 間轉換的程式碼嗎?相信很多人都有用 script 建立程式碼的經驗,今天我們就來聊聊 Kotlin 有什麼好用的工具可以幫助我們建立 .kt 程式碼!

KotlinPoet

其實一開始的標題就已經破題了,我們今天要介紹的就是 KotlinPoet,KotlinPoet 是 square 的其中一個有名的 open source project,像是 Retrofit、leakcanary 等都是它們所開發的喔。

不過還是要先回答我們一開始的問題,為什麼我們會需要一個額外的 library 來幫助我們建立 Kotlin 的程式碼呢?

相信很多人都是使用 StringBuilder 不斷地呼叫 append 把需要的資訊串起來,雖然有點粗暴但好像也沒啥問題,那 KotlinPoet 又能帶給我們什麼好處呢?

其實 square 還有另外一個有名的類似 library - JavaPoet,是專門建立 .java 程式碼的,筆者也是先接觸 JavaPoet,後來才隨著 Kotlin 的流行轉換到 KotinPoet。

以下舉個範例讓讀者感受一下

val greeterClass = ClassName("", "Greeter")
val file = FileSpec.builder("", "HelloWorld")
  .addType(
    TypeSpec.classBuilder("Greeter")
      .primaryConstructor(
        FunSpec.constructorBuilder()
          .addParameter("name", String::class)
          .build()
      )
      .addProperty(
        PropertySpec.builder("name", String::class)
          .initializer("name")
          .build()
      )
      .addFunction(
        FunSpec.builder("greet")
          .addStatement("println(%P)", "Hello, \$name")
          .build()
      )
      .build()
  )
  .addFunction(
    FunSpec.builder("main")
      .addParameter("args", String::class, VARARG)
      .addStatement("%T(args[0]).greet()", greeterClass)
      .build()
  )
  .build()

file.writeTo(System.out)

程式碼雖然很長,但結構非常的易讀且乾淨,它的輸出會是以下的模樣:

class Greeter(val name: String) {
  fun greet() {
    println("""Hello, $name""")
  }
}

fun main(vararg args: String) {
  Greeter(args[0]).greet()
}

有了這個範例,大家應該就可以理解他的好處了吧,我覺得主要在於二個:

  1. 安全:上面的範例大家可以看到有 FileSpecTypeSpecFunSpec 等不同的物件對吧,由於這些額外型別的輔助,使用 KotlinPoet 保證了輸出的結構就一定是合法的,比直接使用 StringBuilder 更安全,在寫複雜邏輯的時候,也更容易知道整體的脈絡。
  2. 方便:上面的範例除了 String 的內容 Hello, \$name 以外,看不到額外插入任何的空白或 tab,這也是 KotlinPoet 的一大好處,由於我們建立的任何 XXXSpec 都隱含著一個樹狀的階層概念,KotlinPoet 可以很輕鬆的幫我們控制這些縮排讓輸出更漂亮易讀。而當我們使用物件的時候,它也會幫我們管理該 import 哪些 package,真的可以說非常方便。

Rules

如上所述 KotlinPoet 有很強的型別限制,那我們就必須要多了解這些規則使用上才能夠如魚得水,我們這邊就來介紹一些基礎結構吧!

FuncSpec

差不多算是最小的 block 單位了,可以在呼叫 addModifiersaddParametersaddStatement 等來擴充。

TypeSpec

一個 type 可能是個 class、annotation class、enum、object、value class 等,都是使用 TypeSpec.XXXBuilder 來建立,而一個 type 通常就可以呼叫 addModifiersaddFunctionssuperclass 等來擴充。

FileSpec

FileSpec 可以算是最大的 block 單位了,由於 Kotlin 裡面有很多 top level 的物件,所以可以包含 TypeSpecFuncSpecPropertySpec 等物件,最後的輸出也會以 FileSpec 最為最上層。

ControlFlow

如果我們想要建立以下的輸出:

fun main() {
  var total = 0
  for (i in 0 until 10) {
    total += i
  }
}

可以直接用 addCode 寫:

val main = FunSpec.builder("main")
  .addCode("""
    |var total = 0
    |for (i in 0 until 10) {
    |    total += i
    |}
    |""".trimMargin())
  .build()

但也可以使用 ControlFlow 相關的 api 讓我們的程式碼看起來更易讀:

val main = FunSpec.builder("main")
  .addStatement("var total = 0")
  .beginControlFlow("for (i in 0 until 10)")
  .addStatement("total += i")
  .endControlFlow()
  .build()

Kotlin 作為一個功能強大的語言,有很多語法糖或特別的 syntax,而通常 KotlinPoet 也會有對應的 class 或 function 可以建立相關的程式碼,但這就需要靠讀者多多研究囉,希望大家會喜歡今天的分享,明天我們將分享讓建立程式碼這件事情變得更加自動化的方法!

Reference

https://square.github.io/kotlinpoet/


上一篇
[Kotlin 全面啟動] SQLDelight II
下一篇
[Kotlin 全面啟動] KSP
系列文
Kotlin 全面啟動 30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言